import {defineComponent, PropType, reactive, ref, Ref, watch} from "vue";
import Style from "@/styles/components/form.module.css"
import {ElForm, ElFormItem, ElInput, ElSelect, ElOption, ElInputNumber, ElRadioGroup, ElCheckboxGroup,
    ElCheckbox, ElRadioButton, ElRadio, ElTree, ElPopover, ElDatePicker, ElConfigProvider, ElUpload,
    ElIcon, ElTag, ElTimeSelect, ElSwitch, ElTreeSelect} from "element-plus";
import type {FormInstance, FormRules, UploadRequestOptions} from "element-plus"
import { Plus } from '@element-plus/icons-vue'
// @ts-ignore
import zhCN from "element-plus/dist/locale/zh-cn.mjs"
import type { FormTree, FormItem} from "@/interfaces/components/Form";
import type {Component} from "@vue/runtime-core";
import type {FormData} from "@/interfaces/components/Page";
import {cloneDeep} from "lodash";
import {Option} from "@/interfaces/components/Form";
import axios from "axios";
import {appStore} from "@/store/modules/app";
import constant from "@/utils/constant";

interface Rule{
    [key: string]: any
}

export default defineComponent({
    name: "CdlForm",
    props: {
        templateTree:{
            type: Object as PropType<FormTree<any>>,
            required: true
        },
        model: {
            type: Object as PropType<FormData<any>>,
            required: true
        },
        init: {
            type: Object as PropType<{[key: string]: ()=> Promise<any>}> ,
            required: false
        },
        class: {
            type: String,
            required: false
        },
        size: {
            type: String as PropType<"large" | "default" | "small">,
            required: false
        },
        labelWidth: {
            type: String,
            required: false,
            default: "100px"
        },
        inline: {
            type: Boolean,
            required: false,
            default: false
        }
    },
    setup(props, {expose}){
        console.log(props.templateTree)
        watch(() => props.model, () => {
            console.log("重新创建表单model")
            ruleForm.value = createFormData(cloneDeep(ruleForm.value))
        }, {deep: true});
        const store = appStore()
        let OldModel: any = undefined
        // 动态表单的配置
        const enableArr = ref<{[key: string]: boolean}>({})
        const changeCallBackArr = ref<{[key: string]: Function}>({})
        // 远程数据
        const serviceOptions = reactive<{[key: string]: any}>({})
        let imageUrls = ref<{[key: string]: string | any[]}>({});
        (()=>{
            if(props.init){
                for(let key in props.init){
                    props.init[key]().then((res:any) =>{
                        serviceOptions[key] = res
                    })
                }
            }
        })();
        const ruleForm = ref(createFormData())
        const ruleFormRef = ref()
        const rules = reactive<FormRules<any>>(createRules())
        init()

        /**
         * 初始化
         */
        function init(){
            initEnableArr()
            pretreatment()
        }
        function pretreatment(){
            props.templateTree.forEach(item =>{
                item.forEach(arr => {
                    if(arr.create){
                        changeCallBackArr.value[`${arr.prop}Create`] = (data: any) => arr.create?.(serviceOptions, data)
                    }
                })
            })
        }
        /**
         * 创建表单校验规则
         */
        function createRules(): FormRules{
            if(!props.templateTree) return {}
            let rulesArr: Rule= {}
            props.templateTree.forEach(item =>{
                item.forEach(arr => {
                    if(arr.rule){
                        rulesArr[arr.prop as string] = arr.rule
                    }
                })
            })
            return rulesArr
        }
        /**
         * 初始化每一项的启用状态
         */
        function initEnableArr(){
            props.templateTree.forEach(item =>{
                item.forEach(arr => {
                    arr.key = `form-item-${arr.prop}`
                    if(arr.enable === undefined) enableArr.value[arr.key] = true
                    else enableArr.value[arr.key] = arr.enable
                })
            })
            console.log("动态表单初始化配置",enableArr.value)
        }
        function changeHandle(val: any, method: Function | undefined, data: any){
            method?.(val, data, enableArr.value, changeCallBackArr.value)
        }
        /**
         * 创建表单数据model对象
         */
        function createFormData(oldData: any = null){
            const obj: {[key:string]: any} = {}
            const model = cloneDeep(props.model)
            for (const item in model){
                if(!oldData) obj[item] = model[item]
                // 新旧模板对比，只更新有差异的值
                else obj[item] = model[item] === OldModel[item]? oldData[item] : model[item]
            }
            // 重置旧模板
            OldModel = cloneDeep(model)
            return obj
        }
        /**
         * 重置表单数据
         */
        function resetting(){
            resetForm(ruleFormRef)
        }
        function resetForm(formEl: Ref<FormInstance | undefined>) {
            if (!formEl.value) return
            formEl.value.resetFields()
        }
        /**
         * 提交数据
         */
        async function parentSubmit(){
            return submitForm(ruleFormRef);
        }
        function submitForm (formEl: Ref<FormInstance | undefined>){
            console.log("表单校验")
            return new Promise((resolve) => {
                if(!formEl.value) return
                formEl.value.validate((valid) => {
                    if (valid) {
                        console.log('submit!')
                        resolve(cloneDeep(ruleForm.value))
                    } else {
                        console.log('error submit!')
                    }
                })
            })
        }

        function handleNodeClick(data: any){
            console.log(data)
        }
        /**
         * 自定义文件上传
         * @param options
         * @param key
         * @param type
         */
        async function uploadRequest(options: UploadRequestOptions, key: string, type: "arr" | "str"){
            const {file, action} = options
            const formData = new FormData();
            formData.append("file", file);
            let res = await axios.post(action, formData)
            if(type === "str") ruleForm.value[key] = res.data.data
            else ruleForm.value[key].push(res.data.data)
        }
        /**
         * 上传成功
         * @param response
         * @param uploadFile
         * @param key
         */
        function handleAvatarSuccess(response: any, uploadFile: any, key: string){
            imageUrls.value[key] = URL.createObjectURL(uploadFile.raw!)
        }
        /**
         * 初始化图片
         * @param key
         * @param data
         */
        function renewImages(key: string, data?: string | string[]){
            if(typeof data === "string") {
                imageUrls.value[key] = ""
                if(data) imageUrls.value[key] = constant.downloadUrl + data
            }else {
                imageUrls.value[key] = []
                data?.map(item => {
                    (imageUrls.value[key] as any[]).push({
                        "url": constant.downloadUrl + item
                    })
                })
            }
        }
        /**
         * 获取词典样式
         * @param key 词典key
         * @param val 词典值
         */
        function getDictStyle(key: string, val: string | number){
            if(!key) return
            let data: any = null
            store.getDict(key).map((item: any) => {
                if(item.dictValue == val) data = item
            })
            return data
        }

        expose({
            parentSubmit,
            resetting
        })
        /**
         * 解析组件
         * @param com
         */
        function parsingComponents(com: FormItem<object>): Component | undefined{
            switch (com.component){
                case "input":
                    return <ElInput  v-model={ruleForm.value[com.prop? com.prop : ""]}
                                     class={Style._input}
                                     style={com.style}
                                     type={com.type}
                                     disabled={com.disabled}
                                     placeholder={com.placeholder}
                                     showWordLimit={true}
                        // @ts-ignore
                                     maxlength={com.max}
                                     rows={com.rows}
                    ></ElInput>
                case "inputNumber":
                    return <ElInputNumber v-model={ruleForm.value[com.prop? com.prop : ""]}
                                          style={com.style}
                                          min={com.min}
                                          max={com.max}></ElInputNumber>
                case "select":
                    // com.create && com.create(serviceOptions, ruleForm.value)
                    return  <ElSelect v-model={ruleForm.value[com.prop? com.prop : ""]}
                                      popperClass={Style._select}
                                      disabled={com.disabled}
                                      filterable={com.filterable}
                                      style={com.style}
                                      multiple={com.multiple}
                                      maxCollapseTags={com.max}
                                      allowCreate={com.allowCreate}
                                      placeholder={com.placeholder}
                                      onChange={(val) => changeHandle(val, com.changeMethod, ruleForm.value)}>
                        {(com.serviceOptionKey? serviceOptions[com.serviceOptionKey] : com.options)?.map((item: Option) =>
                            <ElOption key={item.value}
                                      label={com.optionProps? item[com.optionProps.label] : item.label}
                                      value={com.optionProps? item[com.optionProps.value!] : item.value}>
                                {com.inSlot? com.inSlot(item) : null}
                            </ElOption>)}
                    </ElSelect>
                case "radioButton":
                    return <ElRadioGroup v-model={ruleForm.value[com.prop? com.prop : ""]}
                                         style={com.style}>
                        {com.options?.map(item => <ElRadioButton label={item.value}>{item.label}
                        </ElRadioButton>)}
                    </ElRadioGroup>
                case "radio":
                    return <ElRadioGroup v-model={ruleForm.value[com.prop? com.prop : ""]}
                                         onChange={(val)=> changeHandle(val, com.changeMethod, ruleForm.value)}
                                         style={com.style}
                                         disabled={com.disabled}>
                        {com.options?.map(item => <ElRadio label={com.optionProps? item[com.optionProps.value!] : item.value}
                                                           size={com.size}>
                            {com.optionProps? item[com.optionProps.label] : item.label}
                        </ElRadio>)}
                    </ElRadioGroup>
                case "checkboxGroup":
                    return <ElCheckboxGroup v-model={ruleForm.value[com.prop? com.prop : ""]}>
                        {com.options?.map(item => <ElCheckbox label={item.value} size={com.size}>
                            {item.label}
                        </ElCheckbox>)}
                    </ElCheckboxGroup>
                case "switch":
                    return <ElSwitch v-model={ruleForm.value[com.prop? com.prop : ""]}
                                     active-text="启用"
                                     inactive-text="禁用"
                                     active-value={1}
                                     inactive-value={0}></ElSwitch>
                case "tree":
                    return <ElTree data={com.serviceOptionKey? serviceOptions[com.serviceOptionKey] : {}}
                                   props={com.optionProps} node-click={handleNodeClick} nodeKey="id"></ElTree>
                case "treeSelect":
                    return <ElTreeSelect v-model={ruleForm.value[com.prop? com.prop : ""]}
                                         data={com.serviceOptionKey? serviceOptions[com.serviceOptionKey] : {}}
                                         check-strictly={com.checkStrictly}
                                         placeholder={com.placeholder}
                                         props={com.optionProps}
                                         lazy={com.lazy}
                                         load={com.load}
                                         cacheData={com.cacheData?.(ruleForm.value)}
                                         node-key="id"
                    >
                    </ElTreeSelect>
                case "popover":
                    return <ElPopover>
                        {/*{com.popoverReference && com.popoverReference(ruleForm[com.prop as string])}*/}
                        {/*{com.inSlot && com.inSlot()}*/}
                    </ElPopover>
                case "datePicker":
                    return <ElConfigProvider locale={zhCN}>
                        <ElDatePicker v-model={ruleForm.value[com.prop? com.prop : ""]}
                                      type={com.type as ("date" | "datetime" | "daterange")}
                                      placeholder={com.placeholder}
                                      startPlaceholder={com.startPlaceholder}
                                      defaultTime={com.defaultTime}
                                      endPlaceholder={com.endPlaceholder}
                                      valueFormat={com.valueFormat || "YYYY-MM-DD HH:mm:ss"}>
                        </ElDatePicker>
                    </ElConfigProvider>
                case "timeSelect":
                    return <ElTimeSelect v-model={ruleForm.value[com.prop? com.prop: ""]}
                                         start={"00:00"} step={"00: 15"} end={"23:59"}
                                         maxTime={com.maxTime? ruleForm.value[com.maxTime] : undefined}
                                         minTime={com.minTime? ruleForm.value[com.minTime] : undefined}>
                    </ElTimeSelect>
                case "upload":
                    !imageUrls.value[com.prop!] && renewImages(com.prop? com.prop : "", ruleForm.value[com.prop? com.prop : ""])
                    return <ElUpload action={com.action}
                                     class="avatar-uploader"
                                     showFileList={!!com.showFileList}
                                     listType={com.listType? com.listType : "picture"}
                                     httpRequest={(options)=>
                                         uploadRequest(options, com.prop? com.prop : "", "str")}
                                     onSuccess={(res, file) =>
                                         handleAvatarSuccess(res, file, com.prop? com.prop : "")}>
                        {
                            ruleForm.value[com.prop? com.prop : ""] || imageUrls.value[com.prop? com.prop : ""]?
                                <img src={imageUrls.value[com.prop? com.prop : ""] as string}
                                     class="avatar" alt="" /> :
                                <ElIcon class="avatar-uploader-icon"><Plus/></ElIcon>
                        }
                    </ElUpload>
                case "uploads":
                    !imageUrls.value[com.prop!] && renewImages(com.prop? com.prop : "", ruleForm.value[com.prop? com.prop : ""])
                    return <ElUpload action={com.action}
                                     v-model:file-list={imageUrls.value[com.prop || ""]}
                                     listType="picture-card"
                                     httpRequest={(options)=>
                                         uploadRequest(options, com.prop? com.prop : "", "arr")}>
                        <ElIcon><Plus/></ElIcon>
                    </ElUpload>
                case "tag":
                    const data = getDictStyle(com.dict!, ruleForm.value[com.prop? com.prop : ""])
                    return <ElTag type={data?.labelClass === "primary"? "": data?.labelClass}>
                        {data? data.dictLabel : ruleForm.value[com.prop? com.prop : ""]}
                    </ElTag>
                default:
                    return com.slot && com.slot(com.prop? ruleForm.value[com.prop] : ruleForm.value, (val: any)=>{
                        ruleForm.value[com.prop? com.prop : ""] = val
                    })
            }
        }

        console.log(props.inline)
        return () =>(
            <ElForm
                ref={ruleFormRef}
                model={ruleForm.value}
                rules={rules}
                class={props.class}
                labelWidth={props.labelWidth}
                size={props.size}
                inline={props.inline}>
                {
                    props.templateTree?.map((row: FormItem<any>[]) =>{
                        return <div class={Style.row}>
                            {
                                row.map((item: FormItem<any>) =>{
                                    return enableArr.value[item.key!]?
                                        <ElFormItem class={Style._elItem}
                                                    label={item.label}
                                                    labelWidth={item.labelWidth}
                                                    prop={item.prop}>
                                            {parsingComponents(item)}
                                        </ElFormItem> : null
                                })
                            }
                        </div>
                    })
                }
            </ElForm>
        )
    }
})
